
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.lucene.document.Document;
import org.apache.lucene.search.ScoreDoc;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Tooltip;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
 
public class main extends Application {
	
	final double CHARTHEIGHT = 200;
	final double CHARTWIDTH = 200;
	PieChart chart, chart2;
	TextField query;
	String queryString;
	TextArea correctAns;
	File indexFolder;
	int indexType = 0;
	int similarityType = 0;
	Set<Integer> correctResult = new HashSet<Integer>();
	Set<Integer> actualResult = new HashSet<Integer>();
	List<Integer> feedbackList = new ArrayList<Integer>();
	ScoreDoc[] hits;
	Search searcher;
	Label lblF1;
	String txtF1;
	TextField trInput;
	double threshold = 0.05;
	final ObservableList<Result> data = FXCollections.observableArrayList();
 
    @Override public void start(Stage stage) {
    	
        Scene scene = new Scene(new Group());
        stage.setTitle("Information Retrieval");
        stage.setWidth(800);
        stage.setHeight(660);
        
        TabPane tabPane = new TabPane();
        Tab tabA = new Tab();
        tabA.setText("UI input mode");
        tabPane.getTabs().add(tabA);
        Tab tabB = new Tab();
        tabB.setText("Batch input mode");
        tabPane.getTabs().add(tabB);        
        
        tabA.setContent(createUIModePane(stage));
        tabB.setContent(createBatchModePane(stage));
        
        ((Group) scene.getRoot()).getChildren().add(tabPane);
        stage.setScene(scene);
        stage.show();
    }
    
    public BorderPane createUIModePane(Stage stage){
        BorderPane border = new BorderPane();
        border.setTop(addChooser(stage));
        border.setLeft(addRadioGroupAndInputField());
        border.setCenter(addTable());
        border.setBottom(addButtons());
        border.setRight(addFlowPane());
        return border;
    }
    
    public BorderPane createBatchModePane(Stage stage){
        BatchModeUI bmUI = new BatchModeUI();
        return bmUI.buildBatchModeUI(stage);
    }

    
    public VBox addRadioGroupAndInputField(){
    	final ToggleGroup AnalyzerGroup = new ToggleGroup();
    	final ToggleGroup similarityGroup = new ToggleGroup();
    	
    	RadioButton rbws1 = new RadioButton("Standard");
    	rbws1.setToggleGroup(AnalyzerGroup);
    	rbws1.setUserData(0);
    	rbws1.setSelected(true);
    	
    	RadioButton rbws2 = new RadioButton("Classic");
    	rbws2.setToggleGroup(AnalyzerGroup);
    	rbws2.setUserData(1);
    	
    	RadioButton rbws3 = new RadioButton("Whitespace");
    	rbws3.setToggleGroup(AnalyzerGroup);
    	rbws3.setUserData(2);
    	
    	RadioButton rbws4 = new RadioButton("Stopword");
    	rbws4.setToggleGroup(AnalyzerGroup);
    	rbws4.setUserData(3);
    	
    	RadioButton rbsm1 = new RadioButton("Default Similarity");
    	rbsm1.setToggleGroup(similarityGroup);
    	rbsm1.setUserData(0);
    	rbsm1.setSelected(true);
    	Tooltip tt1 = new Tooltip();
    	tt1.setText(
    		    "Default Similarity Measure for lucene\n"
    		);
    	rbsm1.setTooltip(tt1);
    	
    	RadioButton rbsm2 = new RadioButton("IB Similarity");
    	rbsm2.setToggleGroup(similarityGroup);
    	rbsm2.setUserData(1);
    	Tooltip tt2 = new Tooltip();
    	tt2.setText(
    		    "information-based models\n" +
    		    "Log-logistic distribution\n" +
    		    "Computes lambda as docFreq+1 / numberOfDocuments+1\n" +
    		    "Normalization model that assumes a uniform distribution of the term frequency\n"
    		);
    	rbsm2.setTooltip(tt2);
    	
    	RadioButton rbsm3 = new RadioButton("BM25 Similarity");
    	rbsm3.setToggleGroup(similarityGroup);
    	rbsm3.setUserData(2);
    	Tooltip tt3 = new Tooltip();
    	tt3.setText(
    		    "BM25 Similarity\n"
    		);
    	rbsm3.setTooltip(tt3);
    	
    	RadioButton rbsm4 = new RadioButton("DR Similarity");
    	rbsm4.setToggleGroup(similarityGroup);
    	rbsm4.setUserData(3);
    	Tooltip tt4 = new Tooltip();
    	tt4.setText(
    		    "divergence from randomness similarity measure\n" +
    		    "with Bernoulli processes aftereffect\n" +
    		    "Limiting form of the Bose-Einstein model\n" +
    		    "Normalization model that assumes a uniform distribution of the term frequency\n"
    		);
    	rbsm4.setTooltip(tt4);
    	
    	AnalyzerGroup.selectedToggleProperty().addListener(new ChangeListener<Toggle>(){
    		public void changed(ObservableValue<? extends Toggle>ov,
    				Toggle toggle, Toggle new_toggle){
    			indexType = (int) AnalyzerGroup.getSelectedToggle().getUserData();
    			System.out.println("toggle weighting scheme "+indexType);
    		}
    	});
    	
    	similarityGroup.selectedToggleProperty().addListener(new ChangeListener<Toggle>(){
    		public void changed(ObservableValue<? extends Toggle>ov,
    				Toggle toggle, Toggle new_toggle){
    			similarityType = (int) similarityGroup.getSelectedToggle().getUserData();
    			System.out.println("toggle similarity measure "+similarityType);
    		}
    	});
    	
    	query = new TextField();
    	query.setPromptText("Enter your query");
    	
    	correctAns = new TextArea();
    	correctAns.setPromptText("Enter correct answer");
    	correctAns.setMaxWidth(100);
    	
    	VBox vbox = new VBox();
    	vbox.setPadding(new Insets(10,10,20,10));
    	
    	VBox wsBox = new VBox();
    	Label label1 = new Label();
    	label1.setText("Analyzer");
    	wsBox.setPadding(new Insets(10,10,20,10));
    	wsBox.getChildren().addAll(label1,rbws1, rbws2, rbws3, rbws4);
    	
    	VBox smBox = new VBox();
    	Label label2 = new Label();
    	label2.setText("Similarity Measures");
    	smBox.setPadding(new Insets(10,10,20,10));
    	smBox.getChildren().addAll(label2, rbsm1, rbsm2, rbsm3,rbsm4);
    	
    	vbox.getChildren().addAll(query, wsBox, smBox, correctAns, addTrInput());
    	
    	return vbox;
    }
    public HBox addChooser(final Stage stage){
    	
    	final Label lblIndexFolderPath = new Label("No folder selected");
    	Button button = new Button("Open folder");
    	button.setOnAction(
    			new EventHandler<ActionEvent>(){
    				public void handle(ActionEvent e){
    					indexFolder = openChooser(stage);
    					if(indexFolder != null){
    						lblIndexFolderPath.setText(indexFolder.getPath());
    					}
    				}
    			});

    	
    	HBox hbox = new HBox();
    	hbox.setPadding(new Insets(20,20,20,20));

    	hbox.getChildren().addAll(lblIndexFolderPath,button);
    	return hbox;
    }
    public File openChooser(Stage stage){
    	DirectoryChooser fc = new DirectoryChooser();
    	fc.setTitle("Open File");
    	File file = fc.showDialog(stage);
    	return file;
    }
    public HBox addButtons(){
    	
    	Button btnIndex = new Button("Index");
    	btnIndex.setOnAction(new EventHandler<ActionEvent>() {
    	    @Override public void handle(ActionEvent e) {
    			Index indexer = new Index();
    			try {
    				if(indexFolder != null && indexFolder.exists())
    					indexer.buildIndexes(indexType, similarityType ,indexFolder.getPath());
				} catch (IOException e1) {
					e1.printStackTrace();
				}
    	    }
    	});
    	
    	Button btnSearch = new Button("Search");
    	btnSearch.setOnAction(new EventHandler<ActionEvent>() {
    		   @Override public void handle(ActionEvent e) {
    			   startSearch(false);
    	    }
    	});
    	Button btnFeedback = new Button("Feedback");
    	btnFeedback.setOnAction(new EventHandler<ActionEvent>() {
    	    @Override public void handle(ActionEvent e) {
                RelevantFeedBack rf = new RelevantFeedBack();
                try {
                      String feedback = rf.performRelevantFeedBack(feedbackList);
                      queryString += feedback;
                      startSearch(true);
                    } catch (IOException e1) {
                      e1.printStackTrace();
                    }

    	    }
    	});
    	
    	Button btnCorrectAns = new Button("Load Correct Answer");
    	btnCorrectAns.setOnAction(new EventHandler<ActionEvent>() {
    	    @Override public void handle(ActionEvent e) {
    	    	String rawInput = correctAns.getText();
    	    	String[] tokens = rawInput.split("\\s+");
    	    	correctResult.clear();
    	    	for(int i = 0; i < tokens.length; i++){
    	    		if(tokens[i].length() > 3)
    	    			correctResult.add(Integer.parseInt(tokens[i]));
    	    	}
    	    	updateCharts();
    	    }
    	});
    	
    	
    	HBox hbox = new HBox();
    	hbox.setPadding(new Insets(10,10,20,10));
    	hbox.getChildren().addAll(btnIndex,btnSearch,btnFeedback,btnCorrectAns);
    	
    	return hbox;
    }
    
    public TableView<Result> addTable(){
    	TableView<Result> table = new TableView<Result>();
    	TableColumn docID = new TableColumn("Document");
    	docID.setMinWidth(100);
    	docID.setCellValueFactory(new PropertyValueFactory<Result, String>("doc"));
    	TableColumn hitPoint = new TableColumn("Hit Point");
    	hitPoint.setMinWidth(100);
    	hitPoint.setCellValueFactory(new PropertyValueFactory<Result, String>("hit"));
    	
    	table.setItems(data);
    	table.getColumns().addAll(docID, hitPoint);
    	
    	table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
    	table.getSelectionModel().getSelectedIndices().addListener(new ListChangeListener<Integer>(){
    		public void onChanged(Change<? extends Integer>change){
                //feedbackList
                ObservableList<? extends Integer> lst = change.getList();
                for(int i = 0; i < lst.size(); i++){
                    feedbackList.add(hits[lst.get(i)].doc);
                    Document doc;
                    try {
                        doc = searcher.searcher.doc(hits[lst.get(i)].doc);
                        System.out.print(doc.get("name")+" id:"+hits[lst.get(i)].doc);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
                System.out.println(change.getList().size() +"feedback size");
        }

    	});
    	return table;
    }
    public FlowPane addFlowPane(){
    	FlowPane flow = new FlowPane();
    	flow.setPadding(new Insets(5,0,5,0));
    	flow.setVgap(4);
    	flow.setHgap(4);
    	flow.setPrefWrapLength(170);
    	
        ObservableList<PieChart.Data> pieChartData =
                FXCollections.observableArrayList(
                new PieChart.Data("Grapefruit", 3),
                new PieChart.Data("Oranges", 25),
                new PieChart.Data("Plums", 10),
                new PieChart.Data("Pears", 22),
                new PieChart.Data("Apples", 30));
        chart = new PieChart(pieChartData);
        chart.setTitle("Recall");
        
        ObservableList<PieChart.Data> pieChartData2 =
                FXCollections.observableArrayList(
                new PieChart.Data("Grapefruit", 3),
                new PieChart.Data("Oranges", 25),
                new PieChart.Data("Plums", 10),
                new PieChart.Data("Pears", 22),
                new PieChart.Data("Apples", 30));
        chart2 = new PieChart(pieChartData2);
        chart2.setTitle("Precision");
        
        chart.setAnimated(true);
        chart2.setAnimated(true);
        
        resizeChart(chart);
        resizeChart(chart2);
        
        lblF1 = new Label();
        
        flow.getChildren().add(chart);
        flow.getChildren().add(chart2);
        flow.getChildren().add(lblF1);
        
        return flow;
    }
    
    private void resizeChart(PieChart chart){
    	chart.setPrefSize(CHARTHEIGHT, CHARTWIDTH);
    }
    
    
    public int computeRecall(){
    	int totalResult = correctResult.size();
    	int matchCount = 0;
    	for(Integer i : correctResult){
    		if(actualResult.contains(i)){
    			matchCount ++;
    		}
    	}
    	return matchCount;
    }
    
    public int computePrecision(){
    	int totalResult = actualResult.size();
    	int matchCount = 0;
    	for(Integer i : actualResult){
    		if(correctResult.contains(i)){
    			matchCount ++;
    		}
    	}
    	return matchCount;
    }
    
    
    public void updateCharts(){
    	int recall = computeRecall();
    	int precision = computePrecision();
    	
    	ObservableList<PieChart.Data> testLst = chart.getData();
    	testLst.clear();
    	testLst.add(new PieChart.Data("Recall", recall));
    	testLst.add(new PieChart.Data("Fail", (correctResult.size()-recall)));
    	
    	testLst = chart2.getData();
    	testLst.clear();
    	testLst.add(new PieChart.Data("Precision", precision));
    	testLst.add(new PieChart.Data("Fail", (actualResult.size()-precision)));
    	
      	txtF1 = "";
      	double recallPerc = (double)recall / correctResult.size();
      	double precisionPerc = (double)precision / actualResult.size();
		Double value = (2.0*precisionPerc*recallPerc / (precisionPerc + recallPerc));
		DecimalFormat threeDForm = new DecimalFormat("#.###");
		txtF1 = "F1 = " + threeDForm.format(value);
		lblF1.setText(txtF1);
    }
    
    void startSearch(boolean isFeedback){
    	try {
    		threshold = Double.parseDouble(trInput.getText());
			searcher = new Search();
			if(!isFeedback)
				queryString = query.getText();
			hits = searcher.performSearch(queryString, 10, similarityType, threshold);
			data.clear();
			actualResult = new HashSet<Integer>();
			for (int i = 0; i < hits.length; i++) {
				if (hits[i].score>threshold){
					ScoreDoc hit = hits[i];
					Document doc = searcher.searcher.doc(hits[i].doc); 
					data.add(new Result(doc.get("name"), hit.score + ""));
					actualResult.add(Integer.parseInt(doc.get("name")));
				}
			}
			
			updateCharts();

		} catch (Exception e1) {
			e1.printStackTrace();
		}
    }
 
	private Node addTrInput() {
		VBox vbox = new VBox();
		Label label = new Label();
		
		label.setText("score threshold");
		trInput= new TextField();
    	trInput.setText(new Double(threshold).toString());
    	vbox.getChildren().addAll(label, trInput);
		return vbox;
	}
	
    public static void main(String[] args) {

    	launch(args);

    }
    
    public static class Result {
    	SimpleStringProperty  doc;
    	SimpleStringProperty  hit;
    	
    	public Result(String doc, String hit){
    		this.doc = new SimpleStringProperty (doc);
    		this.hit = new SimpleStringProperty (hit);
    	}
    	
    	public String getDoc(){
    		return doc.get();
    	}
    	
    	public String getHit(){
    		return hit.get();
    	}
    }

}

